﻿using System;
using System.IO;
using System.Reflection;
using System.Runtime.Serialization.Formatters.Binary;
using Common.Logging;
using FluentNHibernate.Cfg;
using FluentNHibernate.Cfg.Db;
using NHibernate.ByteCode.LinFu;
using NHibernate.Context;
using NHibernate.Dialect;
using NHibernate.Driver;
using NHibernate.Tool.hbm2ddl;
using PCE.Configuration.NH.Collections;
using PCE.Configuration.NH.Mappings;

namespace PCE.Configuration.NH
{
	public class ConfiguratorBuilder
	{
		private static readonly ILog Logger = LogManager.GetCurrentClassLogger();
		
		private readonly string _serializedCfgFilePath;

		public ConfiguratorBuilder(string serializedCfgFilePath)
		{
			_serializedCfgFilePath = Path.Combine(
				AppDomain.CurrentDomain.SetupInformation.ApplicationBase,
				serializedCfgFilePath
			);
		}

		public NHibernate.Cfg.Configuration Build()
		{
			var cfg = LoadFromFile();

			if (cfg == null)
			{
				cfg = BuildFresh();

				var export = new SchemaExport(cfg);
				export.Create(false, true);

				SaveToFile(cfg);
			}

			return cfg;
		}

		private NHibernate.Cfg.Configuration LoadFromFile()
		{
			if (string.IsNullOrEmpty(_serializedCfgFilePath) || !IsConfigurationFileValid())
				return null;
			try
			{
				using (var file = File.Open(_serializedCfgFilePath, FileMode.Open, FileAccess.Read, FileShare.ReadWrite))
				{
					var bf = new BinaryFormatter();
					return bf.Deserialize(file) as NHibernate.Cfg.Configuration;
				}
			}
			catch (Exception ex)
			{
				Logger.Error(m => m("Ошибка чтения сериализованной конфигурации NHibernate."), ex);
				return null;
			}
		}

		private bool IsConfigurationFileValid()
		{
			if (!File.Exists(_serializedCfgFilePath))
				return false;

			var configInfo = new FileInfo(_serializedCfgFilePath);

			var asm = Assembly.GetExecutingAssembly();

			// If the assembly is newer, 
			// the serialized config is stale
			var asmInfo = new FileInfo(asm.Location);
			
			if (asmInfo.LastWriteTime > configInfo.LastWriteTime)
				return false;
			
			// If the app.config is newer, 
			// the serialized config is stale
			var appDomain = AppDomain.CurrentDomain;
			
			var appConfigPath = appDomain.SetupInformation.ConfigurationFile;

			var appConfigInfo = new FileInfo(appConfigPath);
			
			if (appConfigInfo.LastWriteTime > configInfo.LastWriteTime)
				return false;

			// It's still fresh
			return true;

		}

		private void SaveToFile(NHibernate.Cfg.Configuration cfg)
		{
			using (var file = File.Open(_serializedCfgFilePath, FileMode.Create, FileAccess.Write, FileShare.None))
			{
				var bf = new BinaryFormatter();
				bf.Serialize(file, cfg);
			}
		}

		private static NHibernate.Cfg.Configuration BuildFresh()
		{
			return Fluently
				.Configure()
				.Database(
					SQLiteConfiguration
						.Standard
						.ConnectionString(
							csb => csb.FromConnectionStringWithKey("pce-db")
						).Dialect<SQLiteDialect>()
						.Driver<SQLite20Driver>()
						.AdoNetBatchSize(100)
						.FormatSql()
						.ShowSql()
						.Raw("hibernate.generate_statistics", "true")
				//.UseReflectionOptimizer()
				).ProxyFactoryFactory<ProxyFactoryFactory>()
				.CurrentSessionContext<WebSessionContext>()
				.CollectionTypeFactory<Net4CollectionTypeFactory>()
				.Mappings(
					mc => mc.FluentMappings.AddFromAssemblyOf<CustomerMap>()
				).BuildConfiguration();
		}
	}
}